// Copyright 2008 Senary
// See credits.txt for details.

// This file is part of Omega VII.

// Omega VII is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Omega VII is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Omega VII.  If not, see <http://www.gnu.org/licenses/>.

#include <utility>

#include "GameManager.h"
#include "Spaceship.h"
#include "Planet.h"
#include "Player.h"
#include "AIPlayer.h"
#include "Constants.h"
#include "SpecialEffects.h"

using namespace std;

Spaceship::Spaceship(SpaceshipType type) : _target(0), _mode(MT_IDLE), _type(type), _tactic(TT_AGRESSIVE)
{
	_shield = maxShield();

	
}

Spaceship::~Spaceship(void)
{
}

// Tries to fly towards the target in an intelligent way
void Spaceship::moveToTarget(float timestep)
{
	// first try face the target
	Vector targetAngle = _target->getPosition() - _position;
	if (targetAngle.r2() < 0.001) {
		return;
	}
	_angle.rotateTowards(targetAngle, SHIP_ROTATION_SPEED * timestep);

	// determine how much we can accelerate
	double accel = 0;
	Vector accelVec(_angle);
	double angleToTarget = angleBetween(_angle, targetAngle);
	if (angleToTarget < M_PI * 0.5) {
		// accelerate fully if we face the target, progressively less
		// if we face slightly another position
		accel = maxAcceleration() * pow(cos(angleToTarget), SHIP_ACCEL_FACTOR);
	}
	accelVec *= (accel / _angle.r());
	_velocity += accelVec * timestep;
	
	// Limit the velocity to what is feasible
	double speed = _velocity.r();
	if (speed > maxSpeed()) {
		_velocity *= (maxSpeed() / speed);
	}
	
}


// inherited from CombatUnit
void Spaceship::attack(CombatUnit* combatUnit, float timestep, float totalTime)
{
	GameManager* gm = GameManager::getInstance();

	CombatUnit::attack(combatUnit, timestep, totalTime);
	
	// Start animation unless we attack a planet (where it looks ugly)
	if (typeid(*_target) != typeid(Planet)) {
		switch(_type) {
			case ST_YATAI_VIII:
			case ST_JONTH:
			case ST_CYSUS:
				gm->getSfxManager().startEffect(this, SFXT_OPEN_FIRE, totalTime);
				break;
			case ST_METHI:
				gm->getSfxManager().startEffect(this, SFXT_METHI_FIRE, totalTime);
				break;
		}
	}
}


void Spaceship::update(float timestep, float totalTime)
{
	GameManager* gm = GameManager::getInstance();

	switch (_mode) {

		case MT_IDLE:
			// come to a stop
			// we simply assume we can break ;-)
			if (_velocity.r2() < maxAcceleration() * maxAcceleration() * timestep * timestep) {
				_velocity = Vector();
			} else {
				_velocity -= _velocity * (maxAcceleration() * timestep / _velocity.r());
			}

		case MT_ORBIT:
			{
				SpaceObject* ne = gm->findNearestEnemy(this);
				if (ne != 0 && isInRange(*ne)) {
						setTarget(ne);
						break;
				}
			}
			// do nothing; everything done by SpaceObject::update
			break;

		case MT_MOVE:
		{
			// can we stop moving?
			if (isInRange(*_target)) {
				if (typeid(*_target) == typeid(Planet)) {
					startOrbit(_target, totalTime);

					if (_owner->isEnemy(_target) || _target->getOwner() == 0) {
						_mode = MT_ATTACK_PLANET;
						break;
					} else {
						_mode = MT_ORBIT;
						break;
					}
				} else if (typeid(*_target) == typeid(Spaceship)) {
					_mode = MT_ATTACK_SPACESHIP_FIRE;
					break;
				}
			}

			// If we're aggressive, check for enemies on the way
			if (_tactic = TT_AGRESSIVE) {
				SpaceObject* ne = gm->findNearestEnemy(this);
				if (ne != 0 && isInRange(*ne)) {
						setTarget(ne);
						break;
				}
			}
			
			// If we hit a planet, orbit around it until we got a free line towards our target
			SpaceObject* p = gm->findNearestPlanet(this);
			if (p != 0 && isInRange(*p)) {
				// Continue if we have a direct line to the target, otherwise simply orbit
				Vector toPlanet = p->getPosition() - _position;
				Vector toTarget = _target->getPosition() - _position;

				if (angleBetween(toPlanet, toTarget) < M_PI * 0.5) {
					startOrbit(p, totalTime);
					_mode = MT_AVOID_PLANET;
					break;
				}
			}
			
			moveToTarget(timestep);
			break;
		}
		case MT_AVOID_PLANET:
		{
			// Continue if we have a direct line to the target, otherwise simply orbit
			Vector toPlanet = _orbitalTarget->getPosition() - _position;
			Vector toTarget = _target->getPosition() - _position;

			if (angleBetween(toPlanet, toTarget) > M_PI * 0.5) {
				setOrbitalTarget(0);
				_mode = MT_MOVE;
			}
			
			break;
		}
		case MT_ATTACK_SPACESHIP_FIRE:
		{
			// Try to face the target
			moveToTarget(timestep);
			Vector newAngle = _target->getPosition() - _position;

			// is the target within fire range?
			if (angleBetween(_angle, newAngle) < MAX_CANNON_ANGLE &&
				newAngle.r2() < FIRE_RANGE * FIRE_RANGE) {

				// wreak havoc ;-)
				Spaceship * ss = dynamic_cast<Spaceship*>(_target);
				attack(ss, timestep, totalTime);
			} else {
				// lost the target... stop firing and turn
				_mode = MT_ATTACK_SPACESHIP_TURN;
				break;
			}

			break;
		}
		case MT_ATTACK_SPACESHIP_TURN:
		{
			// Try to face the target
			//Vector newAngle = _target->getPosition() - _position;
			//_angle.rotateTowards(newAngle, SHIP_ROTATION_SPEED * timestep);
			moveToTarget(timestep);
			Vector newAngle = _target->getPosition() - _position;

			// success?
			if (angleBetween(_angle, newAngle) < MAX_CANNON_ANGLE) {
				_mode = MT_ATTACK_SPACESHIP_FIRE;
				break;
			}

			break;
		}
		case MT_ATTACK_PLANET:
		{
			Planet* p = dynamic_cast<Planet*>(_target);
			// did we conquer the planet already?
			if (p->getOwner() == _owner) {
				_mode = MT_ORBIT;
				break;
			}

			// wreak havoc
			attack(p, timestep, totalTime);
			break;
		}

	}

	// Spaceship always have trails...
	gm->getSfxManager().startEffect(this, SFXT_TRAIL, totalTime);

	// Update the underlying CombatUnit (healing included)
	CombatUnit::update(timestep, totalTime);
}


// getter for the type
SpaceshipType Spaceship::getType() const
{
	return _type;
}


void Spaceship::setTarget(SpaceObject* target)
{
	// don't allow friendly spaceships to be selected as targets
	if (target != 0 && typeid(*target) == typeid(Spaceship)
		&& target->getOwner() == _owner) {
			return;
	}

	// only change something if there's a change
	if (_target != target) {
		_target = target;
		setOrbitalTarget(0);
		if (target != 0) {
			_mode = MT_MOVE;
		} else {
			_mode = MT_IDLE;
		}
	}
}

// change the tactic
void Spaceship::setTactic(TacticType tactic)
{
	_tactic = tactic;
}



// inherited from SpaceObject
void Spaceship::setOrbitalTarget(SpaceObject* target) {
	CombatUnit::setOrbitalTarget(target);
	if (target != 0) {
		_mode = MT_ORBIT;
	}
}

// Inherited from SpaceObject
// This method is called when a spaceship died, so others can react
void Spaceship::somebodyDied(SpaceObject const* who)
{
	if (_target == who) {
		setTarget(0);
	}
}


// Inherited from CombatUnit
// Receive a certain amount of damage
// param damager: the unit that inflicts the damage.
void Spaceship::receiveDamage(double damage, CombatUnit* damager, float totalTime)
{
	if (_shield == maxShield()) {
		// a new combat has started...
		GameManager::getInstance()->appendMessage("Ship "
			+ Spaceship::stringFromSpaceshipType(_type) + " is under attack!");
	}


	// Only receive damage if we're alive... prevents dying multiple times
	if (_shield > 0) {
		CombatUnit::receiveDamage(damage, damager, totalTime);

		// Counterattack depending on the state of the spaceShip
		if (_mode == MT_ATTACK_PLANET || _mode == MT_IDLE ||
			(_mode == MT_MOVE && _tactic == TT_AGRESSIVE)) {
			setTarget(damager);
		}

		GameManager* gm = GameManager::getInstance();
		if (_shield <= 0) {
			explode(_position);
			gm->objectDied(this);
		} else {
			// Smoke if only little energy's left
			if (_shield <= 0.33 * maxShield()) {
				gm->getSfxManager().startEffect(this, SFXT_SMOKE, totalTime);
				gm->getSfxManager().resetEffect(this, SFXT_SMOKE_OFF);
			}

			gm->getSfxManager().startEffect(this, SFXT_RECEIVE_DAMAGE, totalTime);
			
		}
	}
}

// constant values (depend only on type)
// inherited from CombatUnit
int Spaceship::maxFirepower() const
{
	switch(_type){
		case ST_YATAI_VIII:
			return 23; 
			break;
		case ST_JONTH:
			return 35; 
			break;
		case ST_CYSUS:
			return 67; 
			break;
		case ST_METHI:
			return 83; 
			break;
		default:
			throw string("Spaceship::maxFirePower: illegal spaceship type!");
			break;
	}
}

// constant values (depend only on type)
// inherited from CombatUnit
int Spaceship::maxShield() const
{
	switch(_type){
		case ST_YATAI_VIII:
			return 23; 
			break;
		case ST_JONTH:
			return 67; 
			break;
		case ST_CYSUS:
			return 35; 
			break;
		case ST_METHI:
			return 83; 
			break;
		default:
			throw string("Spaceship::maxShield: illegal spaceship type!");
			break;
	}
}

// constant values (depend only on type)
double Spaceship::maxAcceleration(void) const
{
	switch(_type){
		case ST_YATAI_VIII:
			return 0.450 * AU_IN_QUANTS / WEEK_IN_SECONDS * SHIP_ACCEL_TUNING; 
			break;
		case ST_JONTH:
			return 0.300 * AU_IN_QUANTS / WEEK_IN_SECONDS *SHIP_ACCEL_TUNING; 
			break;
		case ST_CYSUS:
			return 0.270 * AU_IN_QUANTS / WEEK_IN_SECONDS * SHIP_ACCEL_TUNING; 
			break;
		case ST_METHI:
			return 0.100 * AU_IN_QUANTS / WEEK_IN_SECONDS * SHIP_ACCEL_TUNING; 
			break;
		default:
			throw string("Spaceship::maxAcceleration: illegal spaceship type!");
			break;
	}
}

// constant values (depend only on type)
double Spaceship::maxSpeed(void) const
{
	switch(_type){
		case ST_YATAI_VIII:
			return 5 * AU_IN_QUANTS / WEEK_IN_SECONDS * SHIP_VELOCITY_TUNING; 
			break;
		case ST_JONTH:
			return 3 * AU_IN_QUANTS / WEEK_IN_SECONDS * SHIP_VELOCITY_TUNING; 
			break;
		case ST_CYSUS:
			return 3 * AU_IN_QUANTS / WEEK_IN_SECONDS * SHIP_VELOCITY_TUNING; 
			break;
		case ST_METHI:
			return 1 * AU_IN_QUANTS / WEEK_IN_SECONDS * SHIP_VELOCITY_TUNING; 
			break;
		default:
			throw string("Spaceship::maxSpeed: illegal spaceship type!");
			break;
	}
}

// constant values (depend only on type)
int Spaceship::upkeep(void) const
{
	switch(_type){
		case ST_YATAI_VIII:
			return 46; 
			break;
		case ST_JONTH:
			return 100; 
			break;
		case ST_CYSUS:
			return 103; 
			break;
		case ST_METHI:
			return 167; 
			break;
		default:
			throw string("Spaceship::upkeep: illegal spaceship type!");
			break;
	}
}

// constant values (depend only on type)
int Spaceship::cost(void) const {
	switch(_type){
		case ST_YATAI_VIII:
			return 460; 
			break;
		case ST_JONTH:
			return 1000; 
			break;
		case ST_CYSUS:
			return 1030; 
			break;
		case ST_METHI:
			return 1670; 
			break;
		default:
			throw string("Spaceship::cost: illegal spaceship type!");
			break;
	}
}

// Checks if this spaceship collides with another object
bool Spaceship::isInRange(SpaceObject const& other) const
{
	// for spaceships, the range is defined by their firing range
	if (typeid(other) == typeid(Spaceship)) {
		return ((_position - other.getPosition()).r2()
				<= FIRE_RANGE * FIRE_RANGE);
	}

	// for planets, the range is defined by their orbiting height
	try {
		Planet const& p = dynamic_cast<Planet const&>(other);
		return ((_position - other.getPosition()).r2()
			<= p.orbitHeight() * p.orbitHeight());
	} catch (bad_cast) {}

	return false;
}


// Inherited from SpaceObject
void Spaceship::setOwner(Player * owner)
{
	if (_owner != 0) {
		_owner->removeSpaceship(this);
	}

	CombatUnit::setOwner(owner);

	if (_owner != 0) {
		_owner->addSpaceship(this);
	}
}


// Convert a string to SpaceshipType
SpaceshipType Spaceship::spaceshipTypeFromString(std::string s)
{
	if (s == "YataiViii") {
		return ST_YATAI_VIII;
	} else if (s == "Jonth") {
		return ST_JONTH;
	} else if (s == "Cysus") {
		return ST_CYSUS;
	} else if (s == "Methi") {
		return ST_METHI;
	}

	throw string("Spaceship::spaceshipTypeFromString: illegal string");
}

// Convert a string to SpaceshipType
string Spaceship::stringFromSpaceshipType(SpaceshipType st)
{
	switch(st) {
		case ST_YATAI_VIII:
			return "YataiViii";
			break;
		case ST_JONTH:
			return "Jonth";
			break;
		case ST_CYSUS:
			return "Cysus";
			break;
		case ST_METHI:
			return "Methi";
			break;
		default:
			throw string("Spaceship::spaceshipTypeFromString: illegal string");
			break;
	}
}
